package com.oxxo.gallery.ftp;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.EventObject;
import java.util.List;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPConnectionClosedException;
import org.apache.commons.net.ftp.FTPReply;
import org.apache.commons.net.io.CopyStreamListener;
import org.apache.commons.net.io.Util;

/**
 * This class allows to upload files and it can be executed from a Thread.
 * @author Ignacio Marmolejo
 */
public class FtpUploadThread implements Runnable {
    private String localFile;
    private String remoteFile;
    private String ftpAddress;
    private String ftpUsername;
    private String ftpPassword;
    private String ftpDir;
    private List<String> errorList;
    private String error;
    private FTPClient ftp;
    private CopyStreamListener listener;
    private Vector<FtpUploadErrorListener> errorListeners;
    private Logger logger = Logger.getLogger(FtpUploadThread.class.getName());

    /*
    public FtpUploadThread() {
        ftp = new FTPClient();
    }
    */

    public FtpUploadThread(String localFile, String remoteFile, String ftpAddress,
            String ftpUsername, String ftpPassword, String ftpDir) {
        this.localFile = localFile;
        this.remoteFile = remoteFile;
        this.ftpAddress = ftpAddress;
        this.ftpUsername = ftpUsername;
        this.ftpPassword = ftpPassword;
        this.ftpDir = ftpDir;
        errorList = new ArrayList<String>();
        errorListeners = new Vector<FtpUploadErrorListener>();
        ftp = new FTPClient();
    }

    public void setListener(CopyStreamListener listener) {
        this.listener = listener;
    }

    public void run() {
        if (!connect())
            return;

        if (!login())
            return;

        if (!uploadFile())
            return;
        
        disconnect();
    }

    private boolean connect() {
        logger.log(Level.INFO, "Connecting to " + ftpAddress + ".");
        try {
            int reply;
            ftp.connect(ftpAddress);
            logger.log(Level.INFO, "Ftp reply string: " + ftp.getReplyString());
            // After connection attempt, you should check the reply code to
            // verify success.
            reply = ftp.getReplyCode();
            logger.log(Level.INFO, "Ftp reply code: " + reply);
            if (!FTPReply.isPositiveCompletion(reply)) {
                ftp.disconnect();
                error = "FTP server refused connection.";
                logger.log(Level.WARNING, error);
                addError(error);
            } else {
                logger.log(Level.INFO, "Connected to " + ftpAddress + ".");
            }
        } catch (SocketException se) {
            logger.log(Level.SEVERE, null, se);
            error = "An error ocurred while trying to connect to the FTP Server";
            addError(error);
        } catch (IOException e) {
            logger.log(Level.SEVERE, null, e);
            error = "An error ocurred while trying to connect to the FTP Server";
            addError(error);
        }

        return isConnected();
    }
    
    public boolean isConnected() {
      return ftp.isConnected();
    }

    private void disconnect() {
        try {
            if (ftp.isConnected()) {
                ftp.logout();
                ftp.disconnect();
                logger.log(Level.INFO,
                        "Loging out and disconnecting from FTP Server");
            }
        } catch (FTPConnectionClosedException ftpcce) {
            logger.log(Level.WARNING, null, ftpcce);
        } catch (IOException ex) {
            logger.log(Level.SEVERE, null, ex);
        }
    }

    private boolean login() {
        try {
            if (!ftp.login(ftpUsername, ftpPassword)) {
                error = "Unable to login to the ftp server";
                logger.log(Level.SEVERE, error);
                addError(error);
                return false;
            }
            if (!ftp.changeWorkingDirectory(ftpDir)) {
                error = "Unable to change to the directory: " + ftpDir;
                logger.log(Level.SEVERE, error);
                addError(error);
                return false;
            }
            logger.log(Level.INFO, ftp.printWorkingDirectory());
        }
        catch (IOException ex) {
            Logger.getLogger(FtpUploadApplet.class.getName()).log(Level.SEVERE,
                    null, ex);
        }

        return true;
    }

    private boolean uploadFile() {
        logger.log(Level.INFO, "Uploading File");
        boolean retValue = false;
        //String localFile = null;
        InputStream inputFile = null;
        OutputStream outputFile = null;

        try {
            //localFile = filePath + File.separator + fileName;
            File file = new File(localFile);
            inputFile = new FileInputStream(file);

            if (ftp.isConnected()) {
                logger.log(Level.INFO, "FTP Connected");
                ftp.enterLocalPassiveMode();
                ftp.setFileType(FTP.BINARY_FILE_TYPE);
                outputFile = ftp.storeFileStream(remoteFile);
                if (outputFile != null) {
                    Util.copyStream(inputFile,
                            outputFile,
                            ftp.getBufferSize(),
                            file.length(),
                            listener,
                            true);
                    inputFile.close();
                    outputFile.close();
                    if (ftp.completePendingCommand()) {
                        logger.log(Level.INFO, "File successfully uploaded");
                        retValue = true;
                    } else {
                        error = "File transfer error";
                        logger.log(Level.INFO, error);
                        addError(error);
                    }
                } else {
                    error = "Error getting outputfile:\n" + ftp.getReplyString();
                    logger.log(Level.SEVERE, error);
                    addError(error);
                    inputFile.close();
                }
            } // end if is connected to the ftp server
        } catch (FileNotFoundException fnfe) {
            logger.log(Level.SEVERE, null, fnfe);
            error = "Local file was not found";
            addError(error);
        } catch (IOException ioe) {
            logger.log(Level.SEVERE, null, ioe);
            error = "Unable to open local file";
            addError(error);
        } // end try-catch block

        return retValue;
    }

    public void addError(String error) {
        errorList.add(error);
        FtpUploadEvent event = new FtpUploadEvent(this, error);
        notifyErrorListeners(event);
    }

    public List<String> getErrorList() {
        return errorList;
    }
    
    public boolean hasErrors() {
      return errorList.size() > 0;
    }
    
    public void addErrorListener(FtpUploadErrorListener listener) {
      if (errorListeners.contains(listener))
        return;
      
      errorListeners.add(listener);
    }
    
    private void notifyErrorListeners(FtpUploadEvent event)
    {
      for (FtpUploadErrorListener errListener : errorListeners) {
        errListener.TransferError(event);
      }
    }
}